#include "lib/pvmcircularbuffer.h"
#include "lib/pvmsystem.h"

PVMCircularBuffer::PVMCircularBuffer()
{
	PVMCircularBuffer(1024, true);
}

PVMCircularBuffer::PVMCircularBuffer(pvm_size size, pvm_bool autoGrow)
{
	buffer = NULL;
	allocBuffer(size);

	head = tail = 0;
	bytesInBuffer = 0;
	lockedState = LOCK_NONE;
	lastLockSize = 0;
	this->autoGrow=autoGrow;
}

PVMCircularBuffer::~PVMCircularBuffer()
{
	freeBuffer();
	head = tail = 0;
	bytesInBuffer = 0;
}

pvm_size PVMCircularBuffer::getFreeSize()
{
	return bufferLength - getUsedSize();
}

pvm_size PVMCircularBuffer::getUsedSize()
{
	return bytesInBuffer;
}

pvm_bool PVMCircularBuffer::makeEmpty()
{
	if(lockedState!=LOCK_NONE)
		return false;

	head = 0;
	tail = 0;
	bytesInBuffer = 0;

	return true;
}

pvm_int PVMCircularBuffer::fetchData(void* outBuffer, pvm_size maxLength)
{
	if(lockedState!=LOCK_NONE)
		return -1;

	pvm_size copySize = maxLength;
	if(copySize>getUsedSize())
		copySize = getUsedSize();

	if(copySize==0)
		return 0;

	if((head<tail)||(copySize<=bufferLength - head))
	{
		memcpy(outBuffer, buffer + head, copySize);
		head += copySize;
		bytesInBuffer -= copySize;
		return 	copySize;
	}

	int portionSize = bufferLength - head;
	memcpy(outBuffer, buffer + head, portionSize);
	bytesInBuffer -= portionSize;

	head = copySize - portionSize;
	memcpy(((pvm_int8*)outBuffer) + portionSize, buffer, head);
	bytesInBuffer -= head;

	return 	copySize;
}

pvm_int PVMCircularBuffer::fetchByte()
{
	if(lockedState!=LOCK_NONE)
		return -1;

	if(getUsedSize()<=0)
		return -1;
	pvm_int retVal = buffer[head++];
	if(head>=bufferLength)
		head = 0;
	bytesInBuffer--;
	return retVal;
}


pvm_int PVMCircularBuffer::readData(void* outBuffer, pvm_size maxLength)
{
	if(lockedState!=LOCK_NONE)
		return -1;

	pvm_size copySize = maxLength;
	if(copySize>getUsedSize())
		copySize = getUsedSize();

	if(copySize<=0)
		return 0;

	pvm_size tmpHead = head;
	pvm_size tmpTail = tail;
	pvm_size tmpBytesInBuffer = bytesInBuffer;

	if((tmpHead<tmpTail)||(copySize<=bufferLength - tmpHead))
	{
		memcpy(outBuffer, buffer + tmpHead, copySize);
		tmpHead += copySize;
		tmpBytesInBuffer -= copySize;
		return 	copySize;
	}

	pvm_size portionSize = bufferLength - tmpHead;
	memcpy(outBuffer, buffer + tmpHead, portionSize);
	tmpBytesInBuffer -= portionSize;

	tmpHead = copySize - portionSize;
	memcpy(((pvm_int8*)outBuffer) + portionSize, buffer, tmpHead);
	tmpBytesInBuffer -= tmpHead;

	return 	copySize;
}

pvm_int PVMCircularBuffer::readByte()
{
	if(lockedState!=LOCK_NONE)
		return -1;

	if(getUsedSize()<=0)
		return -1;

	return buffer[head];
}

pvm_int PVMCircularBuffer::discardData(pvm_size sizeToDiscard)
{
	if(lockedState!=LOCK_NONE)
		return -1;

	if(sizeToDiscard<=0)
	{
		return 0;
	}

	pvm_size discardAvailable = getUsedSize();
	if(sizeToDiscard > discardAvailable)
	{
		if(discardAvailable<=0)
		{
			return -1;
		}
		sizeToDiscard = discardAvailable;
	}

	bytesInBuffer -= sizeToDiscard;
	head += sizeToDiscard;

	if(head>=bufferLength)
		head -= bufferLength;

	return sizeToDiscard;
}

pvm_size PVMCircularBuffer::getMaxWriteSize()
{
	if(head<=tail)
	{
		return bufferLength - tail;
	}
	else
	{
		return head - tail;
	}
}

pvm_int8* PVMCircularBuffer::lockBufferWrite(pvm_size& maxWriteSize)
{
	if(lockedState!=LOCK_NONE)
	{
		maxWriteSize = 0;
		return NULL;
	}

	if(getFreeSize()<=0)
	{
		maxWriteSize = 0;
		return NULL;
	}

	lastLockSize = maxWriteSize = getMaxWriteSize();
	lockedState = LOCK_WRITE;

	return (buffer + tail);
}

pvm_int PVMCircularBuffer::unLockBufferWrite(pvm_size sizeWritten)
{
	if(lockedState!=LOCK_WRITE)
		return -1;

	if(sizeWritten<=0)
	{
		lastLockSize = 0;
		lockedState = LOCK_NONE;
		return 0;
	}

	if(sizeWritten > lastLockSize)
		sizeWritten = lastLockSize;

	pvm_size writeAvailable = getMaxWriteSize();
	if(sizeWritten > writeAvailable)
	{
		if(writeAvailable <= 0)
		{
			lastLockSize = 0;
			lockedState = LOCK_NONE;
			return -1;
		}

		sizeWritten = writeAvailable;
	}

	tail += sizeWritten;
	if(tail>=bufferLength)
	{
		sizeWritten -= tail - bufferLength;
		tail = 0;
	}

	bytesInBuffer += sizeWritten;
	lastLockSize = 0;
	lockedState = LOCK_NONE;

	return sizeWritten;
}

pvm_int8* PVMCircularBuffer::lockBufferRead(pvm_size& maxReadSize)
{
	if(lockedState!=LOCK_NONE)
	{
		maxReadSize = 0;
		return NULL;
	}

	if(getUsedSize()<=0)
	{
		maxReadSize = 0;
		return NULL;
	}

	lockedState = LOCK_READ;

	pvm_int8* retPtr = buffer + head;

	if(head<tail)
		maxReadSize = tail - head;
	else if(head>=tail)
		maxReadSize = bufferLength - head;

	lastLockSize = maxReadSize;

	return retPtr;
}

pvm_int PVMCircularBuffer::unLockBufferRead(pvm_size sizeToDiscard)
{
	if(lockedState!=LOCK_READ)
		return -1;

	if(sizeToDiscard<=0)
	{
		lockedState = LOCK_NONE;
		return 0;
	}

	if(sizeToDiscard > lastLockSize)
		sizeToDiscard = lastLockSize;

	pvm_size discardAvailable = getUsedSize();
	if(sizeToDiscard > discardAvailable)
	{
		if(discardAvailable<=0)
		{
			lockedState = LOCK_NONE;
			return -1;
		}
		sizeToDiscard = discardAvailable;
	}

	if(head<tail && sizeToDiscard > tail - head)
		sizeToDiscard = tail - head;
	else if(head>=tail && sizeToDiscard > bufferLength - head)
		sizeToDiscard = bufferLength - head;


	head += sizeToDiscard;
	bytesInBuffer -= sizeToDiscard;
	lockedState = LOCK_NONE;

	return sizeToDiscard;
}

pvm_int PVMCircularBuffer::grow(pvm_size bytesToAdd)
{
	if(lockedState!=LOCK_NONE)
		return -1;
	pvm_size newSize = bufferLength + bytesToAdd;

	if(newSize<getUsedSize())
		newSize = getUsedSize();

	pvm_int8* newBuffer = (pvm_int8*)PVMMemoryAlloc(newSize);
	if(newBuffer==NULL)
	{
		return bufferLength;
	}

	int newTail = fetchData(newBuffer, newSize);

	PVMMemoryFree(buffer);

	buffer = newBuffer;
	bufferLength = newSize;
	head = 0;
	tail = newTail;
	return bufferLength;
}

pvm_int PVMCircularBuffer::writeData(void* dataPtr, pvm_size len)
{
	if(lockedState != LOCK_NONE)
		return -1;

	pvm_size writeLen = len;
	
	if(writeLen > getFreeSize())
	{
		if (autoGrow)
		{
			pvm_size growSize = bufferLength;
			if(growSize < writeLen)
				growSize = writeLen;

			grow(growSize);
		}
		else
		{
			writeLen = getFreeSize();
		}
	}

	if(writeLen <= 0)
		return writeLen;

	if(tail < head)
	{
		memcpy(buffer + tail, dataPtr, writeLen);
		bytesInBuffer += writeLen;
		tail += writeLen;
		return 	writeLen;
	}
	else if((tail >= head)&&(writeLen <= bufferLength - tail))
	{
		memcpy(buffer + tail, dataPtr, writeLen);
		bytesInBuffer += writeLen;
		tail += writeLen;
		if(tail >= bufferLength)
			tail = 0;
		return 	writeLen;
	}

	int bytesToCopy = bufferLength - tail;
	memcpy(buffer + tail, dataPtr, bytesToCopy);
	tail = 0;

	memcpy(buffer + tail, ((pvm_int8*)dataPtr) + bytesToCopy, writeLen - bytesToCopy);
	tail = writeLen - bytesToCopy;
	bytesInBuffer += writeLen;

	return writeLen;
}

pvm_int PVMCircularBuffer::findFirstByte(pvm_int8 b)
{
	if(getUsedSize()<=0)
		return -1;

	pvm_int8* ptr;

	if(head<tail)
	{
		ptr = (pvm_int8*)memchr(buffer + head, b, tail - head);
		if(ptr==NULL)
			return -1;
		return (buffer - ptr);
	}
	else if(head>=tail)
	{
		ptr = (pvm_int8*)memchr(buffer+ head, b, bufferLength - head);
		if(ptr!=NULL)
			return (buffer- ptr);
		ptr = (pvm_int8*)memchr(buffer, b, tail);
		if(ptr==NULL)
			return -1;
		return (buffer - ptr);
	}

	return -1;
}

bool PVMCircularBuffer::allocBuffer(pvm_size size)
{
	if(buffer)
		return false;

	buffer = (pvm_int8*)PVMMemoryAlloc(size);
	if(buffer!=NULL)
	{
		bufferLength = size;
		return true;
	}
	else
	{
		bufferLength = NULL;
		return true;
	}

}

void PVMCircularBuffer::freeBuffer()
{
	PVMMemoryFree(buffer);
	buffer = NULL;
	bufferLength = 0;
}